
/* Apache License 2.0 */

/*
	文件：yd_aes_cbc_cs.c
	作者：wzh
	邮箱：wangzhihai_138@163.com
	简介：AES算法模式CBC-CS三种变体实现，详情参考《SP800-38A的附件》
	版本：README.md定义
*/

#include "yd_aes_cbc_cs.h"
#include "yd_aes_base.h"


/*
 *	CBC-CS1模式，加密(注：可以不是16字节的倍数)
 *	in： 	待加密数据
 *	out：	加密后数据，in小于16字节时，out存储空间要大于等于16B
 *	key：	密钥(4*AES_NK字节)
 *	iv： 	16字节初始矢量
 *	length:	待加密数据长度
 */
void yd_aes_cbc_cs1_encrypt(uint8_t *in,
							uint8_t *out,
							uint8_t *key,
							uint8_t *iv,
							uint32_t length)
{
	uint8_t i, flag, tmp[16];
	
	if(length == 0)
	{
		return;
	}
	flag = 0;
	if(length > 16)
	{
		flag = 1; //至少2块标志.
	}
	
	do
	{
		if(length > 16) //整块加密.
		{
			for(i=0; i<16; i++)
			{
				out[i] = in[i] ^ iv[i];
			}
			yd_aes_encrypt(out, key);
			iv = out;
			
			in += 16;
			out += 16;
			length -= 16;
		}
		else //1-16B.
		{
			for(i=0; i<length; i++)
			{
				tmp[i] = in[i];
			}
			for(i=length; i<16; i++)
			{
				tmp[i] = 0; //不够16字节，后面填0.
			}
			
			for(i=0; i<16; i++)
			{
				tmp[i] ^= iv[i];
			}
			yd_aes_encrypt(tmp, key); //最后块加密Cn.
			
			if(flag == 1) //至少2块.
			{
				out -= 16;
				out += length; //Cn-1的16-length字节丢弃.
			}
			for(i=0; i<16; i++)
			{
				out[i] = tmp[i]; //不到16字节的，也返回一个完整加密块.
			}
			
			length = 0;
		}
	}while(length > 0);
}

/*
 *	CBC-CS2模式，加密(注：可以不是16字节的倍数)
 *	in： 	待加密数据
 *	out：	加密后数据，in小于16字节时，out存储空间要大于等于16B
 *	key：	密钥(4*AES_NK字节)
 *	iv： 	16字节初始矢量
 *	length:	待加密数据长度
 */
void yd_aes_cbc_cs2_encrypt(uint8_t *in,
							uint8_t *out,
							uint8_t *key,
							uint8_t *iv,
							uint32_t length)
{
	uint8_t i, n, tmp[15];
	uint32_t index;
	
	yd_aes_cbc_cs1_encrypt(in, out, key, iv, length);
	
	/* 不是完整块，交换Cn-1和Cn位置 */
	if(length > 16 && (length & 0x0f) != 0)
	{
		n = length & 0x0f; //Cn-1长度n字节.
		index = length - 16 - n; //定位到Cn-1，Cn长度16字节.
		for(i=0; i<n; i++) //保存Cn-1，并使用Cn交换.
		{
			tmp[i] = out[index+i];
			out[index+i] = out[index+n+i];
		}
		while(i < 16) //Cn的剩余16-n字节.
		{
			out[index+i] = out[index+n+i];
			i++;
		}
		index = length - n; //Cn-1连接Cn位置.
		for(i=0; i<n; i++)
		{
			out[index+i] = tmp[i];
		}
	}
}

/*
 *	CBC-CS3模式，加密(注：可以不是16字节的倍数)
 *	in： 	待加密数据
 *	out：	加密后数据，in小于16字节时，out存储空间要大于等于16B
 *	key：	密钥(4*AES_NK字节)
 *	iv： 	16字节初始矢量
 *	length:	待加密数据长度
 */
void yd_aes_cbc_cs3_encrypt(uint8_t *in,
							uint8_t *out,
							uint8_t *key,
							uint8_t *iv,
							uint32_t length)
{
	uint8_t i, n, tmp[16];
	uint32_t index;
	
	yd_aes_cbc_cs1_encrypt(in, out, key, iv, length);
	
	/* 交换Cn-1和Cn位置 */
	if(length > 16)
	{
		n = length & 0x0f; //Cn-1长度n字节.
		if(n == 0) //完整块.
		{
			n = 16;
		}
		index = length - 16 - n; //定位到Cn-1，Cn长度16字节.
		for(i=0; i<n; i++) //保存Cn-1，并使用Cn交换.
		{
			tmp[i] = out[index+i];
			out[index+i] = out[index+n+i];
		}
		while(i < 16) //Cn的剩余16-n字节.
		{
			out[index+i] = out[index+n+i];
			i++;
		}
		index = length - n; //Cn-1连接Cn位置.
		for(i=0; i<n; i++)
		{
			out[index+i] = tmp[i];
		}
	}
}

/*
 *	CBC-CS1模式，解密(注：可以不是16字节的倍数)
 *	in： 待解密数据
 *	out：解密后数据
 *	key：密钥(4*AES_NK字节)
 *	iv： 16字节初始矢量
 *	length:	待解密数据长度，等于加密数据长度
 */
void yd_aes_cbc_cs1_decrypt(uint8_t *in,
							uint8_t *out,
							uint8_t *key,
							uint8_t *iv,
							uint32_t length)
{
	uint8_t i, n, tmp[16];
	uint32_t index, blk;
	
	if(length == 0)
	{
		return;
	}
	
	if(length > 16) //至少解密2个块.
	{
		/*
		 *	1、最后16字节的首地址
		 *	2、如果length不是16的整数倍，也是Cn-1块的首地址
		 */
		index = length - 16;
		for(i=0; i<16; i++)
		{
			tmp[i] = in[index+i];
		}
		yd_aes_decrypt(tmp, key); //对Cn块解密.
		
		blk = length >> 4; //除16，得到需要解密的块数.
		n = length & 0x0f; //模16.
		if(n != 0)
		{
			for(i=0; i<16-n; i++)
			{
				in[index+i] = tmp[n+i]; //组合Cn-1块.
			}
		}
		else
		{
			blk -= 1; //16的整数倍，需要减1块.
			n = 16;
		}
		
		while(blk > 0) //解密C1至Cn-1块.
		{
			for(i=0; i<16; i++)
			{
				out[i] = in[i];
			}
			yd_aes_decrypt(out, key);
			for(i=0; i<16; i++)
			{
				out[i] ^= iv[i]; //解密数据P1至Pn-1块.
			}
			iv = in;
			
			in += 16;
			out += 16;
			blk -= 1;
		}
		
		in -= 16;
		for(i=0; i<n; i++)
		{
			out[i] = in[i] ^ tmp[i]; //Pn块.
		}
	}
	else //1-16B.
	{
		for(i=0; i<16; i++)
		{
			/* 加密前数据是1-16字节，但加密后产生一个16字节块 */
			tmp[i] = in[i];
		}
		yd_aes_decrypt(tmp, key);
		for(i=0; i<16; i++)
		{
			tmp[i] ^= iv[i];
		}
		
		for(i=0; i<length; i++)
		{
			out[i] = tmp[i]; //取出需要的解密数据.
		}
	}
}

/*
 *	CBC-CS2模式，解密(注：可以不是16字节的倍数)
 *	in： 待解密数据
 *	out：解密后数据
 *	key：密钥(4*AES_NK字节)
 *	iv： 16字节初始矢量
 *	length:	待解密数据长度，等于加密数据长度
 */
void yd_aes_cbc_cs2_decrypt(uint8_t *in,
							uint8_t *out,
							uint8_t *key,
							uint8_t *iv,
							uint32_t length)
{
	uint8_t i, n, tmp[16];
	uint32_t index;
	
	if(length > 16 && (length & 0x0f) != 0)
	{
		n = length & 0x0f; //Cn长度n字节.
		index = length - 16 - n; //定位到Cn-1，Cn-1长度16字节.
		for(i=0; i<n; i++) //保存Cn-1，并使用Cn交换.
		{
			tmp[i] = in[index+i];
			in[index+i] = in[index+16+i];
		}
		while(i < 16) //保存Cn-1剩余的16-n字节.
		{
			tmp[i] = in[index+i];
			i++;
		}
		index = length - 16; //Cn-1连接Cn位置.
		for(i=0; i<16; i++)
		{
			in[index+i] = tmp[i];
		}
	}
	
	yd_aes_cbc_cs1_decrypt(in, out, key, iv, length);
}

/*
 *	CBC-CS3模式，解密(注：可以不是16字节的倍数)
 *	in： 待解密数据
 *	out：解密后数据
 *	key：密钥(4*AES_NK字节)
 *	iv： 16字节初始矢量
 *	length:	待解密数据长度，等于加密数据长度
 */
void yd_aes_cbc_cs3_decrypt(uint8_t *in,
							uint8_t *out,
							uint8_t *key,
							uint8_t *iv,
							uint32_t length)
{
	uint8_t i, n, tmp[16];
	uint32_t index;
	
	if(length > 16)
	{
		n = length & 0x0f; //Cn长度n字节.
		if(n == 0) //完整块.
		{
			n = 16;
		}
		index = length - 16 - n; //定位到Cn-1，Cn-1长度16字节.
		for(i=0; i<n; i++) //保存Cn-1，并使用Cn交换.
		{
			tmp[i] = in[index+i];
			in[index+i] = in[index+16+i];
		}
		while(i < 16) //保存Cn-1剩余的16-n字节.
		{
			tmp[i] = in[index+i];
			i++;
		}
		index = length - 16; //Cn-1连接Cn位置.
		for(i=0; i<16; i++)
		{
			in[index+i] = tmp[i];
		}
	}
	
	yd_aes_cbc_cs1_decrypt(in, out, key, iv, length);
}
